package graph.editor;

import graph.util.Shapes;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Point;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.RectangularShape;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JTextField;

class Edge {
	Vertex v1;
	Vertex v2;
	List<RectangularShape> jointPoints = new ArrayList<RectangularShape>();
	private static final int JOINT_POINT_SIZE = 4;
	private static final int ARROW_SIZE = 10;
	private static final double ARROW_ANGLE = Math.PI / 6;
	JTextField textField = null;
	/* Added by Lam Ho */
	private final int GAP = 5;
	public static int NORMAL_EDGE = 0;
	public static int ENTRY_EDGE = 1;
	public static int EXIT_EDGE = 2;
	private String label = null;
	private int n = 0;
	/*
	 * n is the order of an edge between 2 vertices.
	 * n = 0: this is the first edge
	 * n = 1: this is the second edge, etc.
	 */
	private int type = NORMAL_EDGE;
	/*
	 * type = NORMAL_EDGE: this is the normal edge
	 * type = ENTRY_EDGE: this is the entry edge
	 * type = EXIT_EDGE: this is the exit edge 
	 */
	/* End: Added by Lam Ho */
	
	Edge(Vertex v1, Vertex v2) {
		this.v1 = v1;
		this.v2 = v2;
	}
	
	/* Added by Lam Ho */
	public Edge(Vertex v1, Vertex v2, String label, int n, int type) {
		this.v1 = v1;
		this.v2 = v2;
		this.label = label;
		this.n = n;
		this.type = type;
	}

	public String getLabel() {
		return label;
	}

	public void setLabel(String label) {
		this.label = label;
	}

	public int getType() {
		return type;
	}

	public void setType(int type) {
		this.type = type;
	}
	/* End: Added by Lam Ho */
	
	void addJointPoint() {
		jointPoints
				.add(new Ellipse2D.Double((int) v2.shape.getCenterX(),
						(int) v2.shape.getCenterY(), JOINT_POINT_SIZE,
						JOINT_POINT_SIZE));
	}

	void addJointPoint(Point2D p) {
		jointPoints.add(new Ellipse2D.Double(p.getX(), p.getY(), JOINT_POINT_SIZE, JOINT_POINT_SIZE));
	}
	
	void removeJointPoint(RectangularShape jp) {
		jointPoints.remove(jp);
	}

	RectangularShape getJointPoint(int x, int y) {
		return Shapes.getShape(jointPoints, x, y);
	}

	boolean contains(int x, int y, double precision) {
		double x1 = v1.shape.getCenterX();
		double y1 = v1.shape.getCenterY();
		for (RectangularShape jp : jointPoints) {
			double x2 = jp.getCenterX();
			double y2 = jp.getCenterY();
			if (Line2D.ptLineDist(x1, y1, x2, y2, x, y) < precision)
				return true;
			x1 = x2;
			y1 = y2;
		}
		double x2 = v2.shape.getCenterX();
		double y2 = v2.shape.getCenterY();
		return Line2D.ptLineDist(x1, y1, x2, y2, x, y) < precision;
	}

	private void drawArrow(Graphics2D g2, int x1, int y1, int x2, int y2) {
		double alpha = x1 == x2 ? Math.PI / 2. : Math
				.atan(((y1 - y2) / ((double) (x1 - x2))));
		if (x2 - x1 < 0 || (x1 == x2 && y1 > y2))
			alpha = Math.PI + alpha;

		int x3 = (int) (x2 - ARROW_SIZE * Math.cos(alpha + ARROW_ANGLE));
		int y3 = (int) (y2 - ARROW_SIZE * Math.sin(alpha + ARROW_ANGLE));
		int x4 = (int) (x2 - ARROW_SIZE * Math.cos(alpha - ARROW_ANGLE));
		int y4 = (int) (y2 - ARROW_SIZE * Math.sin(alpha - ARROW_ANGLE));

		g2.drawLine(x2, y2, x3, y3);
		g2.drawLine(x2, y2, x4, y4);
	}
	
	Point labelPosition() {
		int x1, x2, y1, y2;
		int jps = jointPoints.size();
		if (jps <= 1) {
			x1 = (int) v1.shape.getCenterX();
			y1 = (int) v1.shape.getCenterY();
			if (jps == 0) {
				x2 = (int) v2.shape.getCenterX();
				y2 = (int) v2.shape.getCenterY();
			} else {
				RectangularShape rs = jointPoints.get(0);
				x2 = (int) rs.getCenterX();
				y2 = (int) rs.getCenterY();
			}
		} else {
			int index = jointPoints.size() / 2;
			RectangularShape rs1 = jointPoints.get(index - 1);
			RectangularShape rs2 = jointPoints.get(index);
			x1 = (int) rs1.getCenterX();
			y1 = (int) rs1.getCenterY();
			x2 = (int) rs2.getCenterX();
			y2 = (int) rs2.getCenterY();
		}
		/* Modified by Lam Ho */
		/*return new Point((x1 + x2) / 2, (y1 + y2) / 2);*/
		int d = (n % 2 == 0? 1: -1)*n*GAP;
		return new Point((x1 + 2 * x2 + 3 * d) / 3, (y1 + 2 * y2) / 3);
		/* End: Modified by Lam Ho */
	}

	void draw(Graphics2D g2) {
		if (type == NORMAL_EDGE) {
			/* Modified by Lam Ho */
			/*int x1 = (int) v1.shape.getCenterX();
			int y1 = (int) v1.shape.getCenterY();*/
			int d = (n % 2 == 0? 1: -1)*n*GAP;
			int x1 = (int) v1.shape.getCenterX() + d;
			int y1 = (int) v1.shape.getCenterY();
			/* End: Modified by Lam Ho */
			for (RectangularShape jp : jointPoints) {
				int x2 = (int) jp.getCenterX();
				int y2 = (int) jp.getCenterY();
				g2.drawLine(x1, y1, x2, y2);
				/* Modified by Lam Ho */
				/*drawArrow(g2, x1, y1, (3 * x1 + x2) / 4, (3 * y1 + y2) / 4);*/
				/* End: Modified by Lam Ho */
				x1 = x2;
				y1 = y2;
			}
			Paint bg = g2.getPaint();
			g2.setPaint(Color.RED);
			for (RectangularShape jp : jointPoints)
				g2.fill(jp);
			g2.setPaint(bg);
			/* Modified by Lam Ho */
			/*int x2 = (int) v2.shape.getCenterX();*/
			int x2 = (int) v2.shape.getCenterX() + d;
			int y2 = (int) v2.shape.getCenterY();
			/*g2.drawLine(x1, y1, x2, y2);
			drawArrow(g2, x1, y1, (3 * x1 + x2) / 4, (3 * y1 + y2) / 4);*/
			double k = Point2D.distance(x1, y1, x2, y2) / 18;
			int x = (int) (x2 - (x2 - x1)/k);
			int y = (int) (y2 - (y2 - y1)/k);
			g2.drawLine(x1, y1, x, y);
			drawArrow(g2, x1, y1, x, y);
			Point lp = labelPosition();
			if (label != null) {
				g2.setFont(new Font(null, Font.BOLD, 20));
				g2.drawString(label, (int) lp.getX(), (int) lp.getY());
			}
			/* End: Modified by Lam Ho */
			if (textField != null) {
				textField.setLocation(lp);
			}
		} else if (type == EXIT_EDGE || type == ENTRY_EDGE) {
			drawSpecialEdge(g2);
		}
	}
	
	private void drawSpecialEdge(Graphics2D g2) {
		int x1 = (int) v1.shape.getCenterX();
		int y1 = (int) v1.shape.getCenterY();
		int x2 = (int) v2.shape.getCenterX();
		int y2 = (int) v2.shape.getCenterY();
		double k = Point2D.distance(x1, y1, x2, y2) / (GraphComponent.SHAPE_SIZE * 0.5);
		if (type == EXIT_EDGE) {
			x1 = (int) (x1 + (x2 - x1)/k);
			y1 = (int) (y1 + (y2 - y1)/k);
		} else if (type == ENTRY_EDGE) {
			x2 = (int) (x2 - (x2 - x1)/k);
			y2 = (int) (y2 - (y2 - y1)/k);
		}
		g2.drawLine(x1, y1, x2, y2);
		drawArrow(g2, x1, y1, x2, y2);
	}
}
